package net.danielseidl.ludumdare.ld48.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

import net.danielseidl.ludumdare.ld48.Game;
import net.danielseidl.ludumdare.ld48.character.Monster;
import net.danielseidl.ludumdare.ld48.tile.Tile;
import net.danielseidl.ludumdare.ld48.tile.TileMap;

/**
 * Used for rendering the game view.
 *
 * @author Daniel Seidl - info@daniel-seidl.net 
 */
public class GameView extends JFrame
	{
	private static final long serialVersionUID = 4955735323459301399L;
	
	private Logger LOGGER = Logger.getLogger(GameView.class.getName());
	
	private Game game;
	private TileMap tileMap;
	
	private int width;
	private int height;
	
	private BufferedImage image;
	private Graphics2D g2d;
	
	private long firstFrame;
	private int frames;
	private long currentFrame;
	private int fps;
	
	private boolean updateTileMap = true;
	private boolean updateLocation = true;
	
	private HashMap<String, BufferedImage> hudImages = new HashMap<String, BufferedImage>();
	
	/**
	 * Creates a new game view window.
	 * @param width The width of the window.
	 * @param height The height of the window.
	 * @param game The game instance to use.
	 */
	public GameView(int width, int height, Game game)
		{
		this.game = game;
		
		// Saving the window size for later use.
		this.width = width;
		this.height = height;
		
		// Set the title and the default close operation.
		this.setTitle("ld48 game to be named");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		// Do not allow resizing the window and put it in the screen's center.
		this.setBounds(0, 0, this.width, this.height);
		this.setResizable(false);
		this.setVisible(true);
		this.setLocationRelativeTo(null);
				
		// Create a buffered image for the world map view.
		this.image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
		this.g2d = (Graphics2D)image.createGraphics();
		this.g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Load HUD images.
		this.loadHudImages();
		
		// Create a new tile map for the given window size.
		this.tileMap = new TileMap(this.width, this.height, this.game);
						
		// Add player movement.
		this.addPlayerControl();
		
		// Double buffering.
		this.createBufferStrategy(2);
		}
	
	/**
	 * Loads the needed HUD images and stores them for further use.
	 */
	private void loadHudImages()
		{
		for (int i = 0; i < 5; i++)
			{
			try
				{
				this.hudImages.put("heart_" + i, ImageIO.read(new File("gfx/hud/heart_" + i + ".gif")));
				}
			catch (IOException e)
				{
				this.LOGGER.severe("unable to load critical image 'gfx/hud/heart_" + i + ".gif'");
				this.LOGGER.severe("terminating running system...");
				System.exit(1);			
				}
			}		
		
		try
			{
			this.hudImages.put("text_escaped", ImageIO.read(new File("gfx/hud/escaped.png")));
			this.hudImages.put("text_next", ImageIO.read(new File("gfx/hud/next.png")));
			}
		catch (IOException e)
			{
			this.LOGGER.severe("unable to load critical image");
			this.LOGGER.severe("terminating running system...");
			System.exit(1);			
			}
		}
	
	/**
	 * Adds the control structure for player movement.
	 */
	private void addPlayerControl()
		{
		this.addKeyListener(new UserKeyListener(this.game));
		}
	
	/**
	 * Repaints the view of the map by centering the view on the current location of the player.
	 */
	public void repaintView()
		{		
		this.g2d.setColor(Color.BLACK);
		this.g2d.fillRect(0, 0, this.width, this.height);
		
		// Get the needed tiles from the tile map.
		if (this.updateTileMap)
			{
			this.tileMap.updateTilesForView();
			this.updateTileMap = false;
			}
		
		if (this.updateLocation)
			{
			this.tileMap.setPaintStart();
			this.updateLocation = false;
			}
		
		Tile[][] tilesLayerBelow = this.tileMap.getTilesLayerBelowForView();
		Tile[][] tilesLayerLevel = this.tileMap.getTilesLayerLevelForView();
		
		// Starting values needed for painting the tile map.
		int paintX = this.tileMap.getPaintStartX();
		int paintY = this.tileMap.getPaintStartY();
		
		// Paint the map...
		for (int i = 0; i < this.tileMap.getTilesHeight(); i++)
			{
			for (int j = 0; j < this.tileMap.getTilesWidth(); j++)
				{
				this.g2d.drawImage(tilesLayerBelow[j][i].getImage(), paintX, paintY, this);
				if (tilesLayerLevel[j][i] != null)
					{
					this.g2d.drawImage(tilesLayerLevel[j][i].getImage(), paintX, paintY, this);
					}
				paintX += 32;
				}
			paintX = this.tileMap.getPaintStartX();
			paintY += 32;
			}
		
		// FPS...
		this.frames++;
		this.currentFrame = System.currentTimeMillis();
		if (this.currentFrame > this.firstFrame + 1000)
			{
			this.firstFrame = this.currentFrame;
			this.fps = this.frames;
			this.frames = 0;
			}
		
		// Show the monsters.
		this.showMonsters();
		
		// Show the player.
		this.showPlayer();
		
		// Show the player's lifebar.
		this.showLifeBar();
		
		// Show text overlay.
		this.showTextOverlay();

		// Put some debug location information on the screen.
		this.showDebugInfo();		
		
		// Repaint the screen to make all the changes visible.		
		Graphics g = this.getBufferStrategy().getDrawGraphics();
		Dimension dim = this.getSize();
		g.setColor(Color.BLACK);
		g.drawRect(0, 0, dim.width, dim.height);
		g.drawImage(this.image, 0, 0, dim.width, dim.height, 0, 0, this.width, this.height, this);
		this.getBufferStrategy().show();
		}
	
	/**
	 * Shows the monsters.
	 */
	private void showMonsters()
		{
		int[] pLoc = this.game.getPlayer().getLocation();
		int pX = (this.width / 2) - 24;
		int pY = (this.height / 2) - 32;
		for(Monster m : this.game.getLevel().getMonsters())
			{
			int[] mLoc = m.getLocation();
			int mX = pX + ((mLoc[0] - pLoc[0]) * 32) + (mLoc[2] - pLoc[2]);
			int mY = pY + ((mLoc[1] - pLoc[1]) * 32) + (mLoc[3] - pLoc[3]);
			this.g2d.drawImage(m.getCurrentImage(), mX, mY, this);
			}
		}
	
	/**
	 * Shows the player.
	 */
	private void showPlayer()
		{
		this.g2d.drawImage(this.game.getPlayer().getCurrentImage(), (this.width / 2) - 24, (this.height / 2) - 32, this);
		}
	
	/**
	 * Shows debug information.
	 */
	private void showDebugInfo()
		{
		this.g2d.setFont(new Font("Helvetica", Font.BOLD, 12));
		this.g2d.setColor(Color.RED);
		this.g2d.drawString("fps: " + this.fps, this.width - 47, 42);
		}
	
	/**
	 * Shows the player's lifebar.
	 */
	private void showLifeBar()
		{
		int max_lp = this.game.getPlayer().getLifeMax();
		int cur_lp = this.game.getPlayer().getLifeCur();
		
		int hearts = max_lp / 4;
		int hearts_full = cur_lp / 4;
		int last_heart = cur_lp % 4;
		
		int paintX = 10;
		int paintY = 32;
		
		for (int i = 0; i < hearts; i++)
			{
			if (i < hearts_full)
				{
				this.g2d.drawImage(this.hudImages.get("heart_4"), paintX, paintY, this);
				}
			else if (last_heart > 0 && i == hearts_full)
				{
				this.g2d.drawImage(this.hudImages.get("heart_" + last_heart), paintX, paintY, this);
				}
			else
				{
				this.g2d.drawImage(this.hudImages.get("heart_0"), paintX, paintY, this);
				}
			paintX = paintX + 20;
			}
		}
	
	/**
	 * Shows some text overlay if necessary.
	 */
	public void showTextOverlay()
		{
		if (this.game.getPlayerHasEscaped())
			{
			this.g2d.drawImage(this.hudImages.get("text_escaped"), this.width / 2 - 300, this.height / 2 - 45, this);
			}
		else if (this.game.getNextLevelIsLoading())
			{
			this.g2d.setColor(Color.BLACK);
			this.g2d.fillRect(0, 0, this.width, this.height);
			this.g2d.drawImage(this.hudImages.get("text_next"), this.width / 2 - 300, this.height / 2 - 45, this);
			}
		
		this.g2d.setFont(new Font("Helvetica", Font.BOLD, 18));
		this.g2d.setColor(Color.BLACK);
		this.g2d.drawString("LEVEL " + this.game.getLevel().getLevelStage(), 13, this.height - 14);
		this.g2d.setColor(Color.WHITE);
		this.g2d.drawString("LEVEL " + this.game.getLevel().getLevelStage(), 10, this.height - 11);
		}
	
	/**
	 * Forces the game view to update the tile map.
	 */
	public void forceTileMapUpdate()
		{
		this.updateTileMap = true;
		}

	/**
	 * Forces the game view to update the location.
	 */
	public void forceLocationUpdate()
		{
		this.updateLocation = true;
		}
	}